import Header from "@/src/components/layouts/header"; import ContainerPage from "@/src/components/layouts/container-page"; import { StatusBadge } from "@/src/components/layouts/status-badge"; import { Button } from "@/src/components/ui/button"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from "@/src/components/ui/form"; import { Input } from "@/src/components/ui/input"; import { PasswordInput } from "@/src/components/ui/password-input"; import { Switch } from "@/src/components/ui/switch"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/src/components/ui/select"; import { usePostHogClientCapture } from "@/src/features/posthog-analytics/usePostHogClientCapture"; import { blobStorageIntegrationFormSchema, type BlobStorageIntegrationFormSchema, } from "@/src/features/blobstorage-integration/types"; import { useHasProjectAccess } from "@/src/features/rbac/utils/checkProjectAccess"; import { api } from "@/src/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { Card } from "@tremor/react"; import Link from "next/link"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { showSuccessToast } from "@/src/features/notifications/showSuccessToast"; import { showErrorToast } from "@/src/features/notifications/showErrorToast"; import { BlobStorageIntegrationType, BlobStorageIntegrationFileType, BlobStorageExportMode, type BlobStorageIntegration, } from "@langfuse/shared"; import { useLangfuseCloudRegion } from "@/src/features/organizations/hooks"; export default function BlobStorageIntegrationSettings() { const router = useRouter(); const projectId = router.query.projectId as string; const hasAccess = useHasProjectAccess({ projectId, scope: "integrations:CRUD", }); const state = api.blobStorageIntegration.get.useQuery( { projectId }, { enabled: hasAccess, refetchOnMount: false, refetchOnWindowFocus: false, refetchOnReconnect: false, staleTime: 50 * 60 * 1000, // 50 minutes }, ); const status = state.isInitialLoading || !hasAccess ? undefined : state.data?.enabled ? "active" : "inactive"; return ( {status && }, actionButtonsRight: ( ), }} >

Configure scheduled exports of your trace data to AWS S3, S3-compatible storages, or Azure Blob Storage. Set up a hourly, daily, or weekly export to your own storage for data analysis or backup purposes. Use the "Validate" button to test your configuration by uploading a small test file, and the "Run Now" button to trigger an immediate export.

{!hasAccess && (

Your current role does not grant you access to these settings, please reach out to your project admin or owner.

)} {hasAccess && ( <>
)} {state.data?.enabled && ( <>

Data last exported:{" "} {state.data?.lastSyncAt ? new Date(state.data.lastSyncAt).toLocaleString() : "Never (pending)"}

Export mode:{" "} {state.data?.exportMode === BlobStorageExportMode.FULL_HISTORY ? "Full history" : state.data?.exportMode === BlobStorageExportMode.FROM_TODAY ? "From setup date" : state.data?.exportMode === BlobStorageExportMode.FROM_CUSTOM_DATE ? "From custom date" : "Unknown"}

{(state.data?.exportMode === BlobStorageExportMode.FROM_CUSTOM_DATE || state.data?.exportMode === BlobStorageExportMode.FROM_TODAY) && state.data?.exportStartDate && (

Export start date:{" "} {new Date(state.data.exportStartDate).toLocaleDateString()}

)}
)} ); } const BlobStorageIntegrationSettingsForm = ({ state, projectId, isLoading, }: { state?: Partial; projectId: string; isLoading: boolean; }) => { const capture = usePostHogClientCapture(); const { isLangfuseCloud } = useLangfuseCloudRegion(); const [integrationType, setIntegrationType] = useState(BlobStorageIntegrationType.S3); // Check if this is a self-hosted instance (no cloud region set) const isSelfHosted = !isLangfuseCloud; const blobStorageForm = useForm({ resolver: zodResolver(blobStorageIntegrationFormSchema), defaultValues: { type: state?.type || BlobStorageIntegrationType.S3, bucketName: state?.bucketName || "", endpoint: state?.endpoint || null, region: state?.region || "", accessKeyId: state?.accessKeyId || "", secretAccessKey: state?.secretAccessKey || null, prefix: state?.prefix || "", exportFrequency: (state?.exportFrequency || "daily") as | "daily" | "weekly" | "hourly", enabled: state?.enabled || false, forcePathStyle: state?.forcePathStyle || false, fileType: state?.fileType || BlobStorageIntegrationFileType.JSONL, exportMode: state?.exportMode || BlobStorageExportMode.FULL_HISTORY, exportStartDate: state?.exportStartDate || null, }, disabled: isLoading, }); useEffect(() => { setIntegrationType(state?.type || BlobStorageIntegrationType.S3); blobStorageForm.reset({ type: state?.type || BlobStorageIntegrationType.S3, bucketName: state?.bucketName || "", endpoint: state?.endpoint || null, region: state?.region || "auto", accessKeyId: state?.accessKeyId || "", secretAccessKey: state?.secretAccessKey || null, prefix: state?.prefix || "", exportFrequency: (state?.exportFrequency || "daily") as | "daily" | "weekly" | "hourly", enabled: state?.enabled || false, forcePathStyle: state?.forcePathStyle || false, fileType: state?.fileType || BlobStorageIntegrationFileType.JSONL, exportMode: state?.exportMode || BlobStorageExportMode.FULL_HISTORY, exportStartDate: state?.exportStartDate || null, }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [state]); const utils = api.useUtils(); const mut = api.blobStorageIntegration.update.useMutation({ onSuccess: () => { utils.blobStorageIntegration.invalidate(); }, }); const mutDelete = api.blobStorageIntegration.delete.useMutation({ onSuccess: () => { utils.blobStorageIntegration.invalidate(); }, }); const mutRunNow = api.blobStorageIntegration.runNow.useMutation({ onSuccess: () => { utils.blobStorageIntegration.invalidate(); }, }); const mutValidate = api.blobStorageIntegration.validate.useMutation({ onSuccess: (data) => { showSuccessToast({ title: data.message, description: `Test file: ${data.testFileName}`, }); }, onError: (error) => { showErrorToast("Validation failed", error.message); }, }); async function onSubmit(values: BlobStorageIntegrationFormSchema) { capture("integrations:blob_storage_form_submitted"); mut.mutate({ projectId, ...values, }); } const handleIntegrationTypeChange = (value: BlobStorageIntegrationType) => { setIntegrationType(value); blobStorageForm.setValue("type", value); }; return (
( Storage Provider Choose your cloud storage provider )} /> ( {integrationType === "AZURE_BLOB_STORAGE" ? "Container Name" : "Bucket Name"} {integrationType === "AZURE_BLOB_STORAGE" ? "The Azure storage container name" : "The S3 bucket name"} )} /> {/* Endpoint URL field - Only shown for S3-compatible and Azure */} {integrationType !== "S3" && ( ( Endpoint URL {integrationType === "AZURE_BLOB_STORAGE" ? "Azure Blob Storage endpoint URL (e.g., https://accountname.blob.core.windows.net)" : "S3 compatible endpoint URL (e.g., https://play.min.io)"} )} /> )} {/* Region field - Only shown for AWS S3 or compatible storage */} {integrationType !== "AZURE_BLOB_STORAGE" && ( ( Region {integrationType === "S3" ? "AWS region (e.g., us-east-1)" : "S3 compatible storage region"} )} /> )} {/* Force Path Style switch - Only shown for S3-compatible */} {integrationType === "S3_COMPATIBLE" && ( ( Force Path Style Enable for MinIO and some other S3 compatible providers )} /> )} ( {integrationType === "AZURE_BLOB_STORAGE" ? "Storage Account Name" : integrationType === "S3" ? "AWS Access Key ID" : "Access Key ID"} {/* Show optional indicator for S3 types on self-hosted instances with entitlement */} {isSelfHosted && integrationType === "S3" && ( (optional) )} {integrationType === "AZURE_BLOB_STORAGE" ? "Your Azure storage account name" : integrationType === "S3" ? isSelfHosted ? "Your AWS IAM user access key ID. Leave empty to use host credentials (IAM roles, instance profiles, etc.)" : "Your AWS IAM user access key ID" : "Access key for your S3-compatible storage"} )} /> ( {integrationType === "AZURE_BLOB_STORAGE" ? "Storage Account Key" : integrationType === "S3" ? "AWS Secret Access Key" : "Secret Access Key"} {/* Show optional indicator for S3 types on self-hosted instances with entitlement */} {isSelfHosted && integrationType === "S3" && ( (optional) )} {integrationType === "AZURE_BLOB_STORAGE" ? "Your Azure storage account access key" : integrationType === "S3" ? isSelfHosted ? "Your AWS IAM user secret access key. Leave empty to use host credentials (IAM roles, instance profiles, etc.)" : "Your AWS IAM user secret access key" : "Secret key for your S3-compatible storage"} )} /> ( Export Prefix {integrationType === "AZURE_BLOB_STORAGE" ? 'Optional prefix path for exported files in your Azure container (e.g., "langfuse-exports/")' : integrationType === "S3" ? 'Optional prefix path for exported files in your S3 bucket (e.g., "langfuse-exports/")' : 'Optional prefix path for exported files (e.g., "langfuse-exports/")'} )} /> ( Export Frequency How often the data should be exported. Changes are taken into consideration from the next run onwards. )} /> ( File Type The file format for exported data. )} /> ( Export Mode Choose when to start exporting data. "Today" and "Custom date" modes will not include historical data before the specified date. )} /> {blobStorageForm.watch("exportMode") === BlobStorageExportMode.FROM_CUSTOM_DATE && ( ( Export Start Date { const date = e.target.value ? new Date(e.target.value) : null; field.onChange(date); }} placeholder="Select start date" /> Data before this date will not be included in exports )} /> )} ( Enabled )} />
); };